package org.dant.ant.extension.tasks;

import java.io.*;
import java.util.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import org.xml.sax.SAXException;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.optional.junit.DOMUtil;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.util.DOMElementWriter;
import org.apache.tools.ant.util.StringUtils;

import org.dant.ant.extension.xml.XMLConstants;
import org.dant.ant.extension.xml.AggregateTransformer;

public class DantReport extends Task implements XMLConstants {
  public static final String DEFAULT_DIR = ".";
  public static final String DEFAULT_FILENAME = "TESTS-TestSuites.xml";
  
  protected Vector filesets = new Vector();

  protected String toFile;

  protected File toDir;

  protected Vector transformers = new Vector();

  private boolean failOnError = false;
  private Vector failedTests = new Vector();

  public AggregateTransformer createReport() {
    AggregateTransformer transformer = new AggregateTransformer(this);
    transformers.addElement(transformer);
    return transformer;
  }
  
  public void setTofile(String value) {
    toFile = value;
  }
  
  public void setTodir(File value) {
    toDir = value;
  }
  
  public void setFailOnError(boolean failonerror) {
    this.failOnError = failonerror;
  }
  
  public void addFileSet(FileSet fs) {
    filesets.addElement(fs);
  }
  
  public void execute() throws BuildException {
    Element rootElement = createDocument();
    File destFile = getDestinationFile();
    try {
      writeDOMTree(rootElement.getOwnerDocument(), destFile);
    } catch (IOException e) {
      throw new BuildException("Unable to write test aggregate to '" + destFile + "'", e);
    }

    Enumeration enum = transformers.elements();
    while (enum.hasMoreElements()) {
      AggregateTransformer transformer =
        (AggregateTransformer) enum.nextElement();
      transformer.setXmlDocument(rootElement.getOwnerDocument());
      transformer.transform();
    }
    if (failedTests.size() > 0) {
      
      log("The following tests failed:", Project.MSG_WARN);
      Enumeration e = failedTests.elements();
      while (e.hasMoreElements()) {
        log((String)e.nextElement(), Project.MSG_WARN);
      }
      if (failOnError) {
        throw new BuildException("Failed tests detected!");
      }
      
    }
  }
  
  protected File getDestinationFile() {
    if (toFile == null) {
      toFile = DEFAULT_FILENAME;
    }
    if (toDir == null) {
      toDir = project.resolveFile(DEFAULT_DIR);
    }
    return new File(toDir, toFile);
  }
  
  protected File[] getFiles() {
    Vector v = new Vector();
    final int size = filesets.size();
    for (int i = 0; i < size; i++) {
      FileSet fs = (FileSet) filesets.elementAt(i);
      DirectoryScanner ds = fs.getDirectoryScanner(project);
      ds.scan();
      String[] f = ds.getIncludedFiles();
      for (int j = 0; j < f.length; j++) {
        String pathname = f[j];
        if (pathname.endsWith(".xml")) {
          File file = new File(ds.getBasedir(), pathname);
          file = project.resolveFile(file.getPath());
          v.addElement(file);
        }
      }
    }
    
    File[] files = new File[v.size()];
    v.copyInto(files);
    return files;
  }
  
  protected void writeDOMTree(Document doc, File file) throws IOException {
    OutputStream out = null;
    PrintWriter wri = null;
    try {
      out = new FileOutputStream(file);
      wri = new PrintWriter(new OutputStreamWriter(out, "UTF8"));
      wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
      (new DOMElementWriter()).write(doc.getDocumentElement(), wri, 0, "  ");
      wri.flush();
      // writers do not throw exceptions, so check for them.
      if (wri.checkError()) {
        throw new IOException("Error while writing DOM content");
      }
    } finally {
      if (wri != null) {
        wri.close();
        out = null;
            }
      if (out != null) {
        out.close();
      }
    }
  }
  
  protected Element createDocument() {
    DocumentBuilder builder = getDocumentBuilder();
    Document doc = builder.newDocument();
    Element rootElement = doc.createElement(TESTSUITES);
    doc.appendChild(rootElement);
    
    File[] files = getFiles();
    for (int i = 0; i < files.length; i++) {
      try {
        log("Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE);
        //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object
        // will investigate later. It does not use the given directory but
        // the vm dir instead ? Works fine with crimson.
        Document testsuiteDoc = builder.parse("file:///" + files[i].getAbsolutePath());
        Element elem = testsuiteDoc.getDocumentElement();
        // make sure that this is REALLY a testsuite.
        if (TESTCASE.equals(elem.getNodeName())
            || TASK.equals(elem.getNodeName())
            || TESTSUITE.equals(elem.getNodeName())) {
          
          Element copy = (Element) DOMUtil.importNode(rootElement, elem);
          String result = elem.getAttribute(ATTR_RESULT).toUpperCase();
          if ("FAILED".equals(result)) {
            String pkg = elem.getAttribute(ATTR_PACKAGE);
            String name = elem.getAttribute(ATTR_NAME);
            failedTests.add(pkg + "/" + name);
          }
        } else if (TESTSUITE.equals(elem.getNodeName())) {
          // add suite title and raw output dir to RESULTS
          rootElement.setAttribute(ATTR_NAME, elem.getAttribute(ATTR_NAME));
          //  rootElement.setAttribute(ATTR_RAWDATA, elem.getAttribute(ATTR_OWNER));
        } else {
          // issue a warning.
          log("the file " + files[i] + " is not a valid testsuite XML document",
              Project.MSG_WARN);
        }
      } catch (SAXException e) {
        // a testcase might have failed and write a zero-length document,
        // It has already failed, but hey.... mm. just put a warning
        log("The file " + files[i] + " is not a valid XML document. It is possibly corrupted.",
            Project.MSG_WARN);
        log(StringUtils.getStackTrace(e), Project.MSG_DEBUG);
      } catch (IOException e) {
        log("Error while accessing file " + files[i] + ": " + e.getMessage(), Project.MSG_ERR);
      }
    }
    return rootElement;
  }
  
  private static DocumentBuilder getDocumentBuilder() {
    try {
      return DocumentBuilderFactory.newInstance().newDocumentBuilder();
    } catch (Exception exc) {
      throw new ExceptionInInitializerError(exc);
    }
  }
}

